﻿/*
 * Copyright (c) 2003-2020 by AG-Software <info@ag-software.de>
 *
 * All Rights Reserved.
 * See the COPYING file for more information.
 *
 * This file is part of the MatriX project.
 *
 * NOTICE: All information contained herein is, and remains the property
 * of AG-Software and its suppliers, if any.
 * The intellectual and technical concepts contained herein are proprietary
 * to AG-Software and its suppliers and may be covered by German and Foreign Patents,
 * patents in process, and are protected by trade secret or copyright law.
 *
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from AG-Software.
 *
 * Contact information for AG-Software is available at http://www.ag-software.de
 */

namespace Matrix.Extensions.Client.PubSub
{
    using Matrix.Xmpp.Client;
    using Matrix.Xmpp.PubSub;
    using Item = Matrix.Xmpp.PubSub.Item;
    using XData = Xmpp.XData.Data;

    using System.Threading;
    using System.Threading.Tasks;

    public static class DiscoPubSubExtensionsExtensions
    {
        public static int DefaultTimeout { get; set; } = TimeConstants.TwoMinutes;

        #region << create instant node >>
        /// <summary>
        /// Create an instant node
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">Jid of the PubSub service.</param>
        /// <returns></returns>
        /// <remarks>
        /// Instant nodes are nodes whose node is is automatically generated by a pubsub service.
        /// </remarks>
        public static async Task<Iq> CreateInstantNodeAsync(this IClientIqSender iqSender, Jid to)
        {
            return await CreateInstantNodeAsync(iqSender, to, DefaultTimeout, CancellationToken.None);
        }

        /// <summary>
        /// Create an instant node
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">Jid of the PubSub service.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <returns></returns>
        /// <remarks>
        /// Instant nodes are nodes whose node is is automatically generated by a pubsub service.
        /// </remarks>
        public static async Task<Iq> CreateInstantNodeAsync(this IClientIqSender iqSender, Jid to, int timeout)
        {
            return await CreateInstantNodeAsync(iqSender, to, timeout, CancellationToken.None);
        }

        /// <summary>
        /// Create an instant node
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">Jid of the PubSub service.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <param name="cancellationToken">The cancellation token used to cancel the request.</param>
        /// <returns></returns>
        /// <remarks>
        /// Instant nodes are nodes whose node is is automatically generated by a pubsub service.
        /// </remarks>
        public static async Task<Iq> CreateInstantNodeAsync(
            this IClientIqSender iqSender, Jid to, int timeout, CancellationToken cancellationToken)
        {
            return await iqSender.SendIqAsync(PubSubBuilder.CreateInstantNode(to), timeout, cancellationToken);
        }
        #endregion

        #region << create node async >>
        /// <summary>
        /// Creates a new node.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <returns></returns>
        public static async Task<Iq> CreateNodeAsync(this IClientIqSender iqSender, Jid to, string node)
        {
            return await CreateNodeAsync(iqSender, to, node, null);
        }

        /// <summary>
        /// Creates a new node.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <returns></returns>
        public static async Task<Iq> CreateNodeAsync(this IClientIqSender iqSender, Jid to, string node, int timeout)
        {
            return await CreateNodeAsync(iqSender, to, node, null, timeout, CancellationToken.None);
        }

        /// <summary>
        /// Creates a new node.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <param name="cancellationToken">The cancellation token used to cancel the request.</param>
        /// <returns></returns>
        public static async Task<Iq> CreateNodeAsync(
            this IClientIqSender iqSender, Jid to, string node, int timeout, CancellationToken cancellationToken)
        {
            return await CreateNodeAsync(iqSender, to, node, null, timeout, cancellationToken);
        }

        /// <summary>
        /// Creates a new node.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="config">The config.</param>
        /// <returns></returns>
        public static async Task<Iq> CreateNodeAsync(this IClientIqSender iqSender, Jid to, string node, Configure config)
        {
            return await CreateNodeAsync(iqSender, to, node, config, DefaultTimeout, CancellationToken.None);
        }

        /// <summary>
        /// Creates a new node.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="config">The config.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <returns></returns>
        public static async Task<Iq> CreateNodeAsync(this IClientIqSender iqSender, Jid to, string node, Configure config, int timeout)
        {
            return await CreateNodeAsync(iqSender, to, node, config, timeout, CancellationToken.None);
        }

        /// <summary>
        /// Creates a new node.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="config">The config.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <param name="cancellationToken">The cancellation token used to cancel the request.</param>
        /// <returns></returns>
        public static async Task<Iq> CreateNodeAsync(
            this IClientIqSender iqSender, Jid to, string node, Configure config, int timeout, CancellationToken cancellationToken)
        {
            return await iqSender.SendIqAsync(PubSubBuilder.CreateNode(to, node, config), timeout, cancellationToken);
        }
        #endregion

        #region << delete node >>
        /// <summary>
        /// Deletes a node.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <returns></returns>
        public static async Task<Iq> DeleteNodeAsync(this IClientIqSender iqSender, Jid to, string node)
        {
            return await DeleteNodeAsync(iqSender, to, node, DefaultTimeout, CancellationToken.None);
        }

        /// <summary>
        /// Deletes a node.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <returns></returns>
        public static async Task<Iq> DeleteNodeAsync(this IClientIqSender iqSender, Jid to, string node, int timeout)
        {
            return await DeleteNodeAsync(iqSender, to, node, timeout, CancellationToken.None);
        }

        /// <summary>
        /// Deletes a node.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <param name="cancellationToken">The cancellation token used to cancel the request.</param>
        /// <returns></returns>
        public static async Task<Iq> DeleteNodeAsync(
            this IClientIqSender iqSender, Jid to, string node, int timeout, CancellationToken cancellationToken)
        {
            return await iqSender.SendIqAsync(PubSubBuilder.DeleteNode(to, node), timeout, cancellationToken);
        }
        #endregion

        #region << request affiliations list >>
        /// <summary>
        /// Retrieve the affiliations list of a node
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <returns></returns>
        public static async Task<Iq> RequestAffiliationsListAsync(this IClientIqSender iqSender, Jid to, string node)
        {
            return await RequestAffiliationsListAsync(iqSender, to, node, DefaultTimeout, CancellationToken.None);
        }

        /// <summary>
        /// Retrieve the affiliations list of a node
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <returns></returns>
        public static async Task<Iq> RequestAffiliationsListAsync(this IClientIqSender iqSender, Jid to, string node, int timeout)
        {
            return await RequestAffiliationsListAsync(iqSender, to, node, timeout, CancellationToken.None);
        }

        /// <summary>
        /// Retrieve the affiliations list of a node
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <param name="cancellationToken">The cancellation token used to cancel the request.</param>
        /// <returns></returns>
        public static async Task<Iq> RequestAffiliationsListAsync(
            this IClientIqSender iqSender, Jid to, string node, int timeout, CancellationToken cancellationToken)
        {
            return await iqSender.SendIqAsync(PubSubBuilder.RequestAffiliationsList(to, node), timeout, cancellationToken);
        }
        #endregion

        #region << modify affiliation list >>
        /// <summary>
        /// Edit the affiliation of an entity associated with a given node or set the affiliation for a new entity.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">Jid of the PubSub service.</param>
        /// <param name="node">The node.</param>
        /// <param name="affiliation">The affiliation.</param>
        /// <returns></returns>
        public static async Task<Iq> ModifyAffiliationsListAsync(
            this IClientIqSender iqSender, Jid to, string node, Xmpp.PubSub.Owner.Affiliation affiliation)
        {
            return await ModifyAffiliationsListAsync(iqSender, to, node, new[] { affiliation }, DefaultTimeout, CancellationToken.None);
        }

        /// <summary>
        /// Edit the affiliation of an entity associated with a given node or set the affiliation for a new entity.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">Jid of the PubSub service.</param>
        /// <param name="node">The node.</param>
        /// <param name="affiliation">The affiliation.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <returns></returns>
        public static async Task<Iq> ModifyAffiliationsListAsync(
            this IClientIqSender iqSender, Jid to, string node, Xmpp.PubSub.Owner.Affiliation affiliation, int timeout)
        {
            return await ModifyAffiliationsListAsync(iqSender, to, node, new[] { affiliation }, timeout, CancellationToken.None);
        }

        /// <summary>
        /// Edit the affiliations of an entity associated with a given node or set the affiliations for a new entity.
        /// The owner MUST send only modified affiliations (i.e., a "delta"), not the complete list.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">Jid of the PubSub service.</param>
        /// <param name="node">The node.</param>
        /// <param name="affiliations">The affiliations.</param>
        /// <returns></returns>
        public static async Task<Iq> ModifyAffiliationsListAsync(
            this IClientIqSender iqSender, Jid to, string node, Xmpp.PubSub.Owner.Affiliation[] affiliations)
        {
            return await ModifyAffiliationsListAsync(iqSender, to, node, affiliations, DefaultTimeout, CancellationToken.None);
        }

        /// <summary>
        /// Edit the affiliations of an entity associated with a given node or set the affiliations for a new entity.
        /// The owner MUST send only modified affiliations (i.e., a "delta"), not the complete list.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">Jid of the PubSub service.</param>
        /// <param name="node">The node.</param>
        /// <param name="affiliations">The affiliations.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <returns></returns>
        public static async Task<Iq> ModifyAffiliationsListAsync(
            this IClientIqSender iqSender, Jid to, string node, Xmpp.PubSub.Owner.Affiliation[] affiliations, int timeout)
        {
            return await ModifyAffiliationsListAsync(iqSender, to, node, affiliations, timeout, CancellationToken.None);
        }

        /// <summary>
        /// Edit the affiliations of an entity associated with a given node or set the affiliations for a new entity.
        /// The owner MUST send only modified affiliations (i.e., a "delta"), not the complete list.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">Jid of the PubSub service.</param>
        /// <param name="node">The node.</param>
        /// <param name="affiliations">The affiliations.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <param name="cancellationToken">The cancellation token used to cancel the request.</param>
        /// <returns></returns>
        public static async Task<Iq> ModifyAffiliationsListAsync(
            this IClientIqSender iqSender, Jid to, string node, Xmpp.PubSub.Owner.Affiliation[] affiliations, int timeout, CancellationToken cancellationToken)
        {
            return await iqSender.SendIqAsync(PubSubBuilder.ModifyAffiliations(to, node, affiliations), timeout, cancellationToken);
        }
        #endregion

        #region << publish items >>
        /// <summary>
        /// Publishes an item to a node.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="item">The item.</param>
        /// <returns></returns>
        public static async Task<Iq> PublishItemAsync(this IClientIqSender iqSender, Jid to, string node, Item item)
        {
            return await PublishItemsAsync(iqSender, to, node, new[] { item }, DefaultTimeout, CancellationToken.None);
        }

        /// <summary>
        /// Publishes an item to a node.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="item">The item.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <returns></returns>
        public static async Task<Iq> PublishItemAsync(this IClientIqSender iqSender, Jid to, string node, Item item, int timeout)
        {
            return await PublishItemsAsync(iqSender, to, node, new[] { item }, timeout, CancellationToken.None);
        }

        /// <summary>
        /// Publishes an item to a node.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="item">The item.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <param name="cancellationToken">The cancellation token used to cancel the request.</param>
        /// <returns></returns>
        public static async Task<Iq> PublishItemAsync(
            this IClientIqSender iqSender, Jid to, string node, Item item, int timeout, CancellationToken cancellationToken)
        {
            return await PublishItemsAsync(iqSender, to, node, new[] { item }, timeout, cancellationToken);
        }

        /// <summary>
        /// Publishes multiple items to a node.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="item">The item.</param>
        /// <returns></returns>
        public static async Task<Iq> PublishItemsAsync(
            this IClientIqSender iqSender, Jid to, string node, Item[] item)
        {
            return await PublishItemsAsync(iqSender, to, node, item, DefaultTimeout, CancellationToken.None);
        }

        /// <summary>
        /// Publishes multiple items to a node.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="item">The item.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <returns></returns>
        public static async Task<Iq> PublishItemsAsync(
            this IClientIqSender iqSender, Jid to, string node, Item[] item, int timeout)
        {
            return await PublishItemsAsync(iqSender, to, node, item, timeout, CancellationToken.None);
        }

        /// <summary>
        /// Publishes multiple items to a node.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="item">The item.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <param name="cancellationToken">The cancellation token used to cancel the request.</param>
        /// <returns></returns>
        public static async Task<Iq> PublishItemsAsync(
            this IClientIqSender iqSender, Jid to, string node, Item[] item, int timeout, CancellationToken cancellationToken)
        {
            return await iqSender.SendIqAsync(PubSubBuilder.PublishItems(to, node, item), timeout, cancellationToken);
        }
        #endregion

        #region << retract items >>
        /// <summary>
        /// Retracts the item.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">Jid of the pubsub service for this request</param>
        /// <param name="node">The node.</param>
        /// <param name="itemId">The item id.</param>
        /// <returns></returns>
        public static async Task<Iq> RetractItemAsync(this IClientIqSender iqSender, Jid to, string node, string itemId)
        {
            return await RetractItemsAsync(iqSender, to, node, new[] { itemId }, DefaultTimeout, CancellationToken.None);
        }

        /// <summary>
        /// Retracts the item.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">Jid of the pubsub service for this request</param>
        /// <param name="node">The node.</param>
        /// <param name="itemId">The item id.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <returns></returns>
        public static async Task<Iq> RetractItemAsync(this IClientIqSender iqSender, Jid to, string node, string itemId, int timeout)
        {
            return await RetractItemsAsync(iqSender, to, node, new[] { itemId }, timeout, CancellationToken.None);
        }

        /// <summary>
        /// Retracts the items.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">Jid of the pubsub service for this request</param>
        /// <param name="node">The node.</param>
        /// <param name="itemIds">The item ids.</param>
        /// <returns></returns>
        public static async Task<Iq> RetractItemsAsync(this IClientIqSender iqSender, Jid to, string node, string[] itemIds)
        {
            return await RetractItemsAsync(iqSender, to, node, itemIds, DefaultTimeout, CancellationToken.None);
        }

        /// <summary>
        /// Retracts the items.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">Jid of the pubsub service for this request</param>
        /// <param name="node">The node.</param>
        /// <param name="itemIds">The item ids.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <returns></returns>
        public static async Task<Iq> RetractItemsAsync(this IClientIqSender iqSender, Jid to, string node, string[] itemIds, int timeout)
        {
            return await RetractItemsAsync(iqSender, to, node, itemIds, timeout, CancellationToken.None);
        }

        /// <summary>
        /// Retracts the items.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">Jid of the pubsub service for this request</param>
        /// <param name="node">The node.</param>
        /// <param name="itemIds">The item ids.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <param name="cancellationToken">The cancellation token used to cancel the request.</param>
        /// <returns></returns>
        public static async Task<Iq> RetractItemsAsync(
            this IClientIqSender iqSender, Jid to, string node, string[] itemIds, int timeout, CancellationToken cancellationToken)
        {
            return await iqSender.SendIqAsync(PubSubBuilder.RetractItems(to, node, itemIds), timeout, cancellationToken);
        }
        #endregion

        #region << submitnNode configuration >>
        /// <summary>
        /// Submits the node configuration.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="form">The configuration form.</param>
        /// <returns></returns>
        public static async Task<Iq> SubmitNodeConfigurationAsync(this IClientIqSender iqSender, Jid to, string node, XData form)
        {
            return await SubmitNodeConfigurationAsync(iqSender, to, node, form, DefaultTimeout, CancellationToken.None);
        }

        /// <summary>
        /// Submits the node configuration.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="form">The configuration form.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <returns></returns>
        public static async Task<Iq> SubmitNodeConfigurationAsync(this IClientIqSender iqSender, Jid to, string node, XData form, int timeout)
        {
            return await SubmitNodeConfigurationAsync(iqSender, to, node, form, timeout, CancellationToken.None);
        }

        /// <summary>
        /// Submits the node configuration.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="form">The configuration form.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <param name="cancellationToken">The cancellation token used to cancel the request.</param>
        /// <returns></returns>
        public static async Task<Iq> SubmitNodeConfigurationAsync(
            this IClientIqSender iqSender, Jid to, string node, XData form, int timeout, CancellationToken cancellationToken)
        {
            return await iqSender.SendIqAsync(PubSubBuilder.SubmitNodeConfiguration(to, node, form), timeout, cancellationToken);
        }
        #endregion

        #region << subscribe >>
        /// <summary>
        /// Subscribes to a node.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="jid">The jid.</param>
        /// <returns></returns>
        public static async Task<Iq> SubscribeAsync(this IClientIqSender iqSender, Jid to, string node, Jid jid)
        {
            return await SubscribeAsync(iqSender, to, node, jid, DefaultTimeout, CancellationToken.None);
        }

        /// <summary>
        /// Subscribes to a node.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="jid">The jid.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <returns></returns>
        public static async Task<Iq> SubscribeAsync(this IClientIqSender iqSender, Jid to, string node, Jid jid, int timeout)
        {
            return await SubscribeAsync(iqSender, to, node, jid, timeout, CancellationToken.None);
        }

        /// <summary>
        /// Subscribes to a node.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="jid">The jid.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <param name="cancellationToken">The cancellation token used to cancel the request.</param>
        /// <returns></returns>
        public static async Task<Iq> SubscribeAsync(
            this IClientIqSender iqSender, Jid to, string node, Jid jid, int timeout, CancellationToken cancellationToken)
        {
            return await iqSender.SendIqAsync(PubSubBuilder.Subscribe(to, node, jid), timeout, cancellationToken);
        }
        #endregion

        #region << unsubscribe >>
        /// <summary>
        /// Unsubscribes from a node.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="jid">The jid.</param>
        /// <returns></returns>
        public static async Task<Iq> UnsubscribeAsync(this IClientIqSender iqSender, Jid to, string node, Jid jid)
        {
            return await UnsubscribeAsync(iqSender, to, node, null, jid, DefaultTimeout, CancellationToken.None);
        }

        /// <summary>
        /// Unsubscribes from a node.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="jid">The jid.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <returns></returns>
        public static async Task<Iq> UnsubscribeAsync(this IClientIqSender iqSender, Jid to, string node, Jid jid, int timeout)
        {
            return await UnsubscribeAsync(iqSender, to, node, null, jid, timeout, CancellationToken.None);
        }

        /// <summary>
        /// Unsubscribes from a node.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="jid">The jid.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <param name="cancellationToken">The cancellation token used to cancel the request.</param>
        /// <returns></returns>
        public static async Task<Iq> UnsubscribeAsync(
            this IClientIqSender iqSender, Jid to, string node, Jid jid, int timeout, CancellationToken cancellationToken)
        {
            return await UnsubscribeAsync(iqSender, to, node, null, jid, timeout, cancellationToken);
        }

        /// <summary>
        /// Unsubscribes from a node.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="subId">The sub id.</param>
        /// <param name="jid">The jid.</param>
        /// <returns></returns>
        public static async Task<Iq> UnsubscribeAsync(this IClientIqSender iqSender, Jid to, string node, string subId, Jid jid)
        {
            return await UnsubscribeAsync(iqSender, to, node, subId, jid, DefaultTimeout, CancellationToken.None);
        }

        /// <summary>
        /// Unsubscribes from a node.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="subId">The sub id.</param>
        /// <param name="jid">The jid.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <returns></returns>
        public static async Task<Iq> UnsubscribeAsync(this IClientIqSender iqSender, Jid to, string node, string subId, Jid jid, int timeout)
        {
            return await UnsubscribeAsync(iqSender, to, node, subId, jid, timeout, CancellationToken.None);
        }

        /// <summary>
        /// Unsubscribes from a node.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="subId">The sub id.</param>
        /// <param name="jid">The jid.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <param name="cancellationToken">The cancellation token used to cancel the request.</param>
        /// <returns></returns>
        public static async Task<Iq> UnsubscribeAsync(
            this IClientIqSender iqSender, Jid to, string node, string subId, Jid jid, int timeout, CancellationToken cancellationToken)
        {
            return await iqSender.SendIqAsync(PubSubBuilder.Unsubscribe(to, node, subId, jid), timeout, cancellationToken);
        }
        #endregion


        #region << request all subscriptions >>
        /// <summary>
        /// Request all current subscriptions.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <returns></returns>
        public static async Task<Iq> RequestAllSubscriptionsAsync(this IClientIqSender iqSender, Jid to)
        {
            return await RequestAllSubscriptionsAsync(iqSender, to, DefaultTimeout, CancellationToken.None);
        }

        /// <summary>
        /// Request all current subscriptions.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <returns></returns>
        public static async Task<Iq> RequestAllSubscriptionsAsync(this IClientIqSender iqSender, Jid to, int timeout)
        {
            return await RequestAllSubscriptionsAsync(iqSender, to, timeout, CancellationToken.None);
        }

        /// <summary>
        /// Request all current subscriptions.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <param name="cancellationToken">The cancellation token used to cancel the request.</param>
        /// <returns></returns>
        public static async Task<Iq> RequestAllSubscriptionsAsync(
            this IClientIqSender iqSender, Jid to, int timeout, CancellationToken cancellationToken)
        {
            return await iqSender.SendIqAsync(PubSubBuilder.RequestAllSubscriptions(to), timeout, cancellationToken);
        }
        #endregion

        #region << request node configuration >>
        /// <summary>
        /// Requests the node configuration.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <returns></returns>
        public static async Task<Iq> RequestNodeConfigurationAsync(this IClientIqSender iqSender, Jid to, string node)
        {
            return await RequestNodeConfigurationAsync(iqSender, to, node, DefaultTimeout, CancellationToken.None);
        }

        /// <summary>
        /// Requests the node configuration.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <returns></returns>
        public static async Task<Iq> RequestNodeConfigurationAsync(this IClientIqSender iqSender, Jid to, string node, int timeout)
        {
            return await RequestNodeConfigurationAsync(iqSender, to, node, timeout, CancellationToken.None);
        }

        /// <summary>
        /// Requests the node configuration.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <param name="cancellationToken">The cancellation token used to cancel the request.</param>
        /// <returns></returns>
        public static async Task<Iq> RequestNodeConfigurationAsync(
            this IClientIqSender iqSender, Jid to, string node, int timeout, CancellationToken cancellationToken)
        {
            return await iqSender.SendIqAsync(PubSubBuilder.RequestNodeConfiguration(to, node), timeout, cancellationToken);
        }
        #endregion

        #region << request subscriptions >>
        /// <summary>
        /// Request the subscriptions from a specific node.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <returns></returns>
        public static async Task<Iq> RequestSubscriptionsAsync(this IClientIqSender iqSender, Jid to, string node)
        {
            return await RequestSubscriptionsAsync(iqSender, to, node, DefaultTimeout, CancellationToken.None);
        }

        /// <summary>
        /// Request the subscriptions from a specific node.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <returns></returns>
        public static async Task<Iq> RequestSubscriptionsAsync(this IClientIqSender iqSender, Jid to, string node, int timeout)
        {
            return await RequestSubscriptionsAsync(iqSender, to, node, timeout, CancellationToken.None);
        }

        /// <summary>
        /// Request the subscriptions from a specific node.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <param name="cancellationToken">The cancellation token used to cancel the request.</param>
        /// <returns></returns>
        public static async Task<Iq> RequestSubscriptionsAsync(
            this IClientIqSender iqSender, Jid to, string node, int timeout, CancellationToken cancellationToken)
        {
            return await iqSender.SendIqAsync(PubSubBuilder.RequestSubscriptions(to, node), timeout, cancellationToken);
        }
        #endregion

        #region << request all items >>
        /// <summary>
        /// Request all items of node.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <returns>Task&lt;Iq&gt;.</returns>
        public static async Task<Iq> RequestAllItemsAsync(this IClientIqSender iqSender, Jid to, string node)
        {
            return await RequestAllItemsAsync(iqSender, to, node, DefaultTimeout, CancellationToken.None);
        }

        /// <summary>
        /// Request all items of node.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <returns>Task&lt;Iq&gt;.</returns>
        public static async Task<Iq> RequestAllItemsAsync(this IClientIqSender iqSender, Jid to, string node, int timeout)
        {
            return await RequestAllItemsAsync(iqSender, to, node, timeout, CancellationToken.None);
        }

        /// <summary>
        /// Request all items of node.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <param name="cancellationToken">The cancellation token used to cancel the request.</param>
        /// <returns>Task&lt;Iq&gt;.</returns>
        public static async Task<Iq> RequestAllItemsAsync(
            this IClientIqSender iqSender, Jid to, string node, int timeout, CancellationToken cancellationToken)
        {
            return await iqSender.SendIqAsync(PubSubBuilder.RequestAllItems(to, node), timeout, cancellationToken);
        }
        #endregion

        #region << request subscriptions list >>
        /// <summary>
        /// Retrieve Subscriptions List
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <returns></returns>
        public static async Task<Iq> RequestSubscriptionsListAsync(this IClientIqSender iqSender, Jid to, string node)
        {
            return await RequestSubscriptionsListAsync(iqSender, to, node, DefaultTimeout, CancellationToken.None);
        }

        /// <summary>
        /// Retrieve Subscriptions List
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <returns></returns>
        public static async Task<Iq> RequestSubscriptionsListAsync(this IClientIqSender iqSender, Jid to, string node, int timeout)
        {
            return await RequestSubscriptionsListAsync(iqSender, to, node, timeout, CancellationToken.None);
        }

        /// <summary>
        /// Retrieve Subscriptions List
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">To.</param>
        /// <param name="node">The node.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <param name="cancellationToken">The cancellation token used to cancel the request.</param>
        /// <returns></returns>
        public static async Task<Iq> RequestSubscriptionsListAsync(
            this IClientIqSender iqSender, Jid to, string node, int timeout, CancellationToken cancellationToken)
        {
            return await iqSender.SendIqAsync(PubSubBuilder.RequestSubscriptionsList(to, node), timeout, cancellationToken);
        }
        #endregion

        #region << purge node >>
        /// <summary>
        /// Purges all node item.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">Jid of the pubsub service for this request</param>
        /// <param name="node">The node.</param>
        /// <returns></returns>
        /// <remarks>
        /// If a service persists published items, a node owner may want to purge
        /// the node of all published items (thus removing all items from the
        /// persistent store, with the exception of the last published item,
        /// which MAY be cached). It is OPTIONAL for a service to implement this
        /// feature.
        /// </remarks>
        public static async Task<Iq> PurgeNodeAsync(this IClientIqSender iqSender, Jid to, string node)
        {
            return await PurgeNodeAsync(iqSender, to, node, DefaultTimeout, CancellationToken.None);
        }

        /// <summary>
        /// Purges all node item.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">Jid of the pubsub service for this request</param>
        /// <param name="node">The node.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <returns></returns>
        /// <remarks>
        /// If a service persists published items, a node owner may want to purge
        /// the node of all published items (thus removing all items from the
        /// persistent store, with the exception of the last published item,
        /// which MAY be cached). It is OPTIONAL for a service to implement this
        /// feature.
        /// </remarks>
        public static async Task<Iq> PurgeNodeAsync(this IClientIqSender iqSender, Jid to, string node, int timeout)
        {
            return await PurgeNodeAsync(iqSender, to, node, timeout, CancellationToken.None);
        }

        /// <summary>
        /// Purges all node item.
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">Jid of the pubsub service for this request</param>
        /// <param name="node">The node.</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <param name="cancellationToken">The cancellation token used to cancel the request.</param>
        /// <returns></returns>
        /// <remarks>
        /// If a service persists published items, a node owner may want to purge
        /// the node of all published items (thus removing all items from the
        /// persistent store, with the exception of the last published item,
        /// which MAY be cached). It is OPTIONAL for a service to implement this
        /// feature.
        /// </remarks>
        public static async Task<Iq> PurgeNodeAsync(
            this IClientIqSender iqSender, Jid to, string node, int timeout, CancellationToken cancellationToken)
        {
            return await iqSender.SendIqAsync(PubSubBuilder.PurgeNode(to, node), timeout, cancellationToken);
        }
        #endregion

        #region << request item >>
        /// <summary>
        /// Request an item
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">Jid of the pubsub service for this request</param>
        /// <param name="node">The node.</param>
        /// <param name="id">The id</param>
        /// <returns></returns>
        public static async Task<Iq> RequestItemAsync(
            this IClientIqSender iqSender, Jid to, string node, string id)
        {
            return await RequestItemAsync(iqSender, to, node, id, DefaultTimeout, CancellationToken.None);
        }

        /// <summary>
        /// Request an item
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">Jid of the pubsub service for this request</param>
        /// <param name="node">The node.</param>
        /// <param name="id">The id</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <returns></returns>
        public static async Task<Iq> RequestItemAsync(
            this IClientIqSender iqSender, Jid to, string node, string id, int timeout)
        {
            return await RequestItemAsync(iqSender, to, node, id, timeout, CancellationToken.None);
        }

        /// <summary>
        /// Request an item
        /// </summary>
        /// <param name="iqSender">The <see cref="IClientIqSender"/></param>
        /// <param name="to">Jid of the pubsub service for this request</param>
        /// <param name="node">The node.</param>
        /// <param name="id">The id</param>
        /// <param name="timeout">The timeout in milliseconds.</param>
        /// <param name="cancellationToken">The cancellation token used to cancel the request.</param>
        /// <returns></returns>
        public static async Task<Iq> RequestItemAsync(
            this IClientIqSender iqSender, Jid to, string node, string id, int timeout, CancellationToken cancellationToken)
        {
            return await iqSender.SendIqAsync(PubSubBuilder.RequestItem(to, node, id), timeout, cancellationToken);
        }
        #endregion
    }
}
